iT邦幫忙

2025 iThome 鐵人賽

DAY 5
2
Vue.js

在 Vue 過氣前要學的三十件事系列 第 5

在 Vue 過氣前要學的第五件事 - 主動還是被動

  • 分享至 

  • xImage
  •  

前言

在上一篇 在 Vue 過氣前要學的第四件事 - 2025 了還要用 .value ?
我們講了 refreactive 之間的差異,這篇就接著說入門會接觸的第二種易混淆 API。

就是 computed & watch ,第一次看到可能會覺得說,
有點像,但又說不出哪裡像。

感覺都是去抓某個值,然後執行下一步操作,

至少我一開始學是有搞混的,還是只有我那麼白癡

但其實只要看了官方文件加上一些 Sample code 就蠻好解釋差異的,
那最後還會補充說明一下 watchwatchEffect 的差異。

使用

computed

先來講計算屬性 computed
我不知道大家看到這個計算屬性是有什麼感覺。

const sum = computed(() => 1 + 1);
// 是這樣嗎?
const hasBook = computed(() => (author.books.length > 0 ? "Yes" : "No"));
// 還是這樣?
const articleTitle = computed(() => article.title);
// 那這樣算嗎?

計算這兩個字對於我來說很難有精準的一個描述。

那我們來看一下這段 :

推薦使用計算屬性來描述依賴響應式狀態的複雜邏輯 - computed #基礎示例

OK,所以我們可以把這個計算屬性看做是工廠的處理器 input 進去,output 出來我們所要的格式
https://ithelp.ithome.com.tw/upload/images/20250904/20172784ZWrMkxXnxE.png

<script setup>
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})
</script>
<template>
    <span>我 {{ author.books.length > 0 ? "有" : "沒有" }} 書</span>
    <!-- 我有書 -->
</template>

假設你今天在做一個線上讀書網站,並且想在<template>表示說,
喔這個作者是否有著作,那你可能會用上面這種寫法。

這樣有個問題,把邏輯寫在 <template> 其實不是那麼方便閱讀,
而且你可能在多個地方都要用的這個值, 當你這樣貼來貼去,無疑是增加了 bug 的風險。

因此這種對值執行邏輯操作,舉凡拼接、計算、判斷等等,都可以用 computed 來省去許多功夫。

小提醒:
如果你是第一次看到這種寫法,
這個叫做 條件 (三元) 運算子,語法是 條件 ? 條件成立返回的值 : 條件不成立返回的值
用來做簡易的邏輯操作是很好用的寫法,但沒運用好會造成可讀性變差。

上面的程式碼也會變成這樣 :

<script setup>
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})
+ const hasBook = computed(() => (author.books.length > 0 ? "有" : "沒有"));
</script>
<template>
-    <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
+    <span>我 {{ hasBook }} 書</span>
     <!-- 我有書 -->
</template>

那我們就實作了一個很簡單的計算屬性。

最佳實踐

  1. 你不該在 computed 內執行副作用
  2. 不該對 computed 重新賦值,因為 computed 返回值只是一個臨時狀態,不代表原值。

副作用 :
對外部環境做的操作可以統稱為副作用,這個外面通常是指不被 {} 包住的區域,
常見的有修改全局變量、打 API、修改 DOM 元素、etc.

watch

接下來輪到偵聽器 watch

如果說剛剛的 computed 是用來做邏輯操作?
watch 不能拿來做邏輯操作嗎?

const hasSnowman = ref(false);
watch(
  () => product.snowman.length,
  () => {
    hasSnowman.value = product.snowman.length > 0 ? "產線開始生產了" : "他們在裝忙";
  },
  { immediate: true }
);
console.log(hasSnowman.value); // 產線開始生產了

其實也可以,但有沒有注意到一件事,我必須更改外部參數,而不是直接使用。
(示範用例子,這樣寫其實不是那麼清楚)

這就是 watch 的核心概念,他會去監聽某個資料,要是有變更,那就會執行後面的動作
https://ithelp.ithome.com.tw/upload/images/20250904/20172784MojuPWVs2C.png

所以到底差在哪?

計算屬性允許我們聲明性地計算衍生值。然而在有些情況下,我們需要在狀態變化時執行一些“副作用”:例如更改 DOM,或是根據異步操作的結果去修改另一處的狀態。 vue-docs

其實這邊官方文件就很好的說明了 computedwatch 的差異。

簡單的統整就是,
computed 是用來對依賴值操作,並返回一個值,
watch 則是觀察某個依賴項變化, 再去執行副作用

一定要用 computed 嗎? 我用 function 也可以對數值操作阿

<script setup>
// 略...
function hasBook() {
  return author.books.length > 0;
}
</script>

<template>
  <p>{{ hasBook() }}</p>
  <!-- true -->
</template>

確實,但 computed 有一個優點,就是會有 cache ( 快取? 緩存?

也就是說只要你的依賴項沒有更改的情況,
你每次呼叫這個 function 都會返回之前計算的結果,也就省去重複計算的效能。

Q: watch 跟 watchEffect 差在哪?


有時候你會遇到 source 跟 callback 都用到同一個值的情況

const todoId = ref(1)
const data = ref(null)

watch(
  todoId, // 這邊監聽了 todoId
  async () => {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/todos/${todoId.value}` // 這邊也用到了 todoId
    )
    data.value = await response.json()
  },
  { immediate: true }
)

等於說你用了兩次 todoId,這時候就可以考慮用 watchEffect,


- watch(
-  todoId, // 這邊監聽了 todoId
-  async () => {
+ watchEffect(async () => {
   const response = await fetch(
     `https://jsonplaceholder.typicode.com/todos/${todoId.value}` // 這邊也用到了 todoId
   )
  data.value = await response.json()
+ })
-  },
-  { immediate: true }
-)

瞬間少一大段,超爽的,上面主要的差異有兩個

  1. 不用傳入 source 參數。
  2. 不用寫 { immediate: true },還會自動追蹤依賴項。

所以誰是主動誰是被動?

Both.
在預設情況下, computedwatch 都是懶執行( lazy )
這個好處是如果依賴項沒有更改,那就會回傳上次執行的值。

不過有個小問題就是,初始化進來其實不會啟動

那該怎麼辦呢? 其實我們上面有寫到,就是使用 { immediate: true }
這會讓 watch 從 lazy 變成 eager,初始化的時候會先啟動一次,然後接續後面的監聽。

Q: 我需要停止監聽嗎? 會不會造成 memory leaks ?

… will be automatically stopped when the owner component is unmounted. In most cases, you don't need to worry about stopping the watcher yourself.

… 並在卸載所有者元件時自動停止。在大多數情況下,您無需擔心自己停止 watcher。

OK 爽。
好啦還是有例外,如果你今天要執行異步操作,像這樣。

<script setup>
import { watchEffect } from 'vue'

// 它會自動停止
watchEffect(() => {})

// ...這個則不會!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>

這種情況就要手動停止, 不過這應該是少數情況

const unwatch = watchEffect(() => {})

// ...當該偵聽器不再需要時
unwatch()

結語

這篇文章原本只是要探討 computedwatch 的差異,
但不知不覺就打好多ㄌ,其實還有一些沒有講到。

一句話解釋何時該用 watch 何時該用 computed
只要沒有需要更改外部環境,請先用 computed

例如用來清理副作用並重新啟動的 onWatcherCleanup
在資料更新當下馬上觸發副作用,優先級更高的 watchSyncEffect

但很多都是有用到再去文件查就好了,對官方文件做筆記梳理差異還是能找到許多沒想過的東西。
如果你喜歡這個系列或是想看我發瘋, 歡迎按下 訂閱 一起走完這三十天吧

一些小練習

  1. computedwatch 在觸發執行的行為上有什麼差異?
    誰是惰性(lazy)運算,誰是主動(eager)監聽?
  2. watchwatchEffect 各自的使用時機。

資料來源

  1. Vue - computed
  2. Vue - watcher
    https://ithelp.ithome.com.tw/upload/images/20250905/20172784sRTsSR5Y9y.png

上一篇
在 Vue 過氣前要學的第四件事 - 2025 了還要用 .value ?
下一篇
在 Vue 過氣前要學的第六件事 - 響應式到底為什麼那麼重要
系列文
在 Vue 過氣前要學的三十件事18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言